package com.epam.cdp.selenium2.core.common.remote_access.ftp;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.SocketException;
import java.util.ArrayList;

import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.apache.commons.net.ftp.FTPReply;

import com.epam.cdp.selenium2.core.common.SpecialSymbols;
import com.epam.cdp.selenium2.core.results.logger.Logger;

/**
 * Work via FTP protocol
 * 
 * @author yyudzitski
 */
public class FtpUtils
{
  /**
   * Message used in work with FTP
   * 
   * @author yyudzitski
   */
  private enum FtpMessages
  {
    FTP_CONNECT_PATTERN( "FTP connect to ftp://%s:%s@%s." ),
    SERVER_ERROR( "FTP error." ),
    FTP_REPLY_STRING_PATTERN( "FTP reply string: %s." ),
    CHANGE_WORKING_DIR_PATTERN("FTP change working directory to: %s." ),
    FILE_EXISTS_PATTERN( "FTP file exists: %s." ),
    FILE_NOT_EXISTS_PATTERN("FTP file not exists: %s." ),
    SOURCE_DIR_PATTERN( "Source dir: %s." ),
    UPLOAD_ERROR( "Cannot upload file via ftp." ),
    OPERATION_GET_FILE_LIST( "FTP get list of file." ),
    OPERATION_UPLOAD_FILE_PATTERN( "FTP upload file: %s." ),
    OPERATION_DELETE_FILE_PATTERN( "FTP delete file: %s." ),
    OPERATION_DOWNLOAD_FILE_PATTERN("FTP dowload file: %s."),
    OPERATION_RENAME_FILE("FTP rename file/folder: %s."),
    OPEARTION_CREATE_DIR_PATTERN("FTP create folder: %s."),
    OPERATION_GET_WORK_DIR_PATTERN("Get working directory on: %s.");

    private String message;

    private FtpMessages( String message )
    {
      this.message = message;
    }

    @Override
    public String toString()
    {
      return message;
    }
  };

  /**
   * Modes for search on FTP server
   * 
   * @author yyudzitski
   */
  private enum SearchMode
  {
    FILES_AND_FOLDERS( 0 ), FILES_ONLY( 1 ), FOLDERS_ONLY( 2 );

    private int mode;

    private SearchMode( int mode )
    {
      this.mode = mode;
    }

    @SuppressWarnings( "unused" )
    public int getMode()
    {
      return mode;
    }

    @Override
    public String toString()
    {
      return String.valueOf( mode );
    }
  };

  public boolean isFileExist( FtpHost ftpAccess, String fileName )
  {
    boolean result = false;
    try
    {
      FTPClient ftp = connect( ftpAccess );
      result = isFileExist( ftp, fileName );
      disconnect( ftp );
      return result;
    }
    catch( Exception e )
    {
      Logger.debug( FtpMessages.SERVER_ERROR.toString(), e );
    }
    return result;
  }

  public boolean isFileExist( FtpHost ftpAccess, String fileName, String targerDirectory )
  {
    boolean result = false;
    try
    {
      FTPClient ftp = connect( ftpAccess );
      ftp.changeWorkingDirectory( targerDirectory );
      result = isFileExist( ftp, fileName );
      disconnect( ftp );
      return result;
    }
    catch( Exception e )
    {
      Logger.debug( FtpMessages.SERVER_ERROR.toString(), e );
    }
    return result;
  }

  private boolean isFileExist( FTPClient ftp, String fileName )
  {
    try
    {
      Logger.operation( FtpMessages.OPERATION_GET_FILE_LIST.toString() );
      FTPFile[] files = ftp.listFiles();
      Logger.info( String.format( FtpMessages.FTP_REPLY_STRING_PATTERN.toString(), ftp.getReplyString() ) );
      for( FTPFile file : files )
      {
        if( file.getName().equals( fileName ) )
        {
          Logger.info( String.format( FtpMessages.FILE_EXISTS_PATTERN.toString(), fileName ) );
          return true;
        }
      }
    }
    catch( IOException e )
    {
      Logger.debug( FtpMessages.SERVER_ERROR.toString(), e );
    }
    Logger.info( String.format( FtpMessages.FILE_NOT_EXISTS_PATTERN.toString(), fileName ) );
    return false;
  }

  public boolean uploadFile( FtpHost ftpAccess, String fileName, String sourceDir, String destDir ) throws Exception
  {
    boolean result = false;
    Logger.operation( String.format( FtpMessages.OPERATION_UPLOAD_FILE_PATTERN.toString(), fileName ) );
    FTPClient ftp = connect( ftpAccess );
    Logger.info( String.format( FtpMessages.CHANGE_WORKING_DIR_PATTERN.toString(), destDir ) );
    Logger.info( String.format( FtpMessages.SOURCE_DIR_PATTERN.toString(), sourceDir ) );
    ftp.changeWorkingDirectory( destDir );
    File sourceFile = new File( sourceDir + File.separator + fileName );
    FileInputStream fis = new FileInputStream( sourceFile );
    ftp.setFileType( FTP.BINARY_FILE_TYPE );
    boolean upload = ftp.storeFile( fileName, fis );
    Logger.info( String.format( FtpMessages.FTP_REPLY_STRING_PATTERN.toString(), ftp.getReplyString() ) );
    if( upload || ( !upload && isFileExist( ftp, fileName ) ) )
    {
      ftp.sendCommand( "site chmod 0755 " + fileName );
      Logger.info( ftp.getReplyString() );
      result = true;
    }
    disconnect( ftp );
    return result;
  }

  public void uploadFile( FtpHost ftpAccess, File file, String remoteDir ) throws IOException
  {
    FTPClient ftp = null;
    FileInputStream fis = null;
    Logger.operation( String.format( FtpMessages.OPERATION_UPLOAD_FILE_PATTERN.toString(), file.getName() ) );
    try
    {
      ftp = connect( ftpAccess );
      ftp.changeWorkingDirectory( remoteDir );
      fis = new FileInputStream( file );
      ftp.setFileType( FTP.BINARY_FILE_TYPE );
      if( !isFileExist( ftp, file.getName() ) )
      {
        if( !ftp.storeFile( file.getName(), fis ) )
        {
          throw new RuntimeException( FtpMessages.UPLOAD_ERROR.toString() );
        }
      }
    }
    finally
    {
      if( ftp != null )
      {
        disconnect( ftp );
      }
      if( fis != null )
      {
        fis.close();
      }
    }
  }

  public boolean downloadFile( FtpHost ftpAccess, String fileName, String sourceDir, String destDir ) throws Exception
  {
    boolean result = false;
    Logger.operation( String.format( FtpMessages.OPERATION_DOWNLOAD_FILE_PATTERN.toString(), fileName ) );
    FTPClient ftp = connect( ftpAccess );
    ftp.changeWorkingDirectory( sourceDir );
    String targetDir =
      destDir.substring( destDir.length() - 1, destDir.length() ).equals( File.separator ) ? destDir : destDir
        + File.separator;
    File file = new File( targetDir + fileName );
    FileOutputStream fos = new FileOutputStream( file );
    ftp.setFileType( FTP.BINARY_FILE_TYPE );
    if( ftp.retrieveFile( fileName, fos ) )
    {
      result = true;
    }
    disconnect( ftp );
    return result;
  }

  public boolean deleteFile( FtpHost ftpAccess, String fileName, String sourceDir ) throws Exception
  {
    boolean result = false;
    Logger.operation( String.format( FtpMessages.OPERATION_DELETE_FILE_PATTERN.toString(), fileName ) );
    FTPClient ftp = connect( ftpAccess );
    Logger.info( String.format( FtpMessages.CHANGE_WORKING_DIR_PATTERN.toString(), sourceDir ) );
    ftp.changeWorkingDirectory( sourceDir );
    if( ftp.deleteFile( fileName ) )
    {
      Logger.info( String.format( FtpMessages.FTP_REPLY_STRING_PATTERN.toString(), ftp.getReplyString() ) );
      result = true;
    }
    Logger.info( String.format( FtpMessages.FTP_REPLY_STRING_PATTERN.toString(), ftp.getReplyString() ) );
    disconnect( ftp );
    return result;
  }

  public boolean checkSuccessConnect( FtpHost ftpAccess )
  {
    FTPClient client;
    try
    {
      client = connect( ftpAccess );
    }
    catch( Exception e )
    {
      throw new RuntimeException( e );
    }
    return client.isConnected();
  }

  public boolean checkFailConnect( FtpHost ftpAccess )
  {
    FTPClient client;
    try
    {
      client = connect( ftpAccess );
      if( client.getReplyString().contains( String.valueOf( FTPReply.NOT_LOGGED_IN ) ) )
      {
        return true;
      }
    }
    catch( Exception e )
    {
      throw new RuntimeException( e );
    }
    return false;
  }

  public FTPClient connect( FtpHost ftpAccess ) throws SocketException, IOException
  {
    Logger.operation( String.format( FtpMessages.FTP_CONNECT_PATTERN.toString(), ftpAccess.getLogin(),
      ftpAccess.getPassword(), ftpAccess.getHostName() ) );
    FTPClient ftp = new FTPClient();
    ftp.connect( ftpAccess.getHostName() );
    ftp.login( ftpAccess.getLogin(), ftpAccess.getPassword() );
    ftp.enterLocalPassiveMode();
    Logger.info( String.format( FtpMessages.FTP_REPLY_STRING_PATTERN.toString(), ftp.getReplyString() ) );
    return ftp;
  }

  public FTPClient connect( String connectString ) throws SocketException, IOException
  {
    FTPClient ftp = new FTPClient();
    ftp.connect( connectString );
    ftp.enterLocalPassiveMode();
    return ftp;
  }

  public void disconnect( FTPClient ftp )
  {
    if( ftp.isConnected() )
    {
      try
      {
        ftp.logout();
      }
      catch( IOException e )
      {
        Logger.debug( e.getMessage() );
      }
      finally
      {
        try
        {
          ftp.disconnect();
        }
        catch( IOException e )
        {
        }
      }
    }
  }

  private String[] getFiles( FtpHost ftpAccess, String sourceDir, SearchMode mode ) throws SocketException, IOException
  {
    ArrayList<String> filesList = new ArrayList<String>();
    FTPClient ftp = connect( ftpAccess );
    Logger.info( String.format( FtpMessages.CHANGE_WORKING_DIR_PATTERN.toString(), sourceDir ) );
    ftp.changeWorkingDirectory( sourceDir );
    Logger.info( String.format( FtpMessages.FTP_REPLY_STRING_PATTERN.toString(), ftp.getReplyString() ) );
    FTPFile[] files = ftp.listFiles();
    for( FTPFile file : files )
    {
      switch( mode )
      {
        case FILES_AND_FOLDERS:
          filesList.add( file.getName() );
          break;
        case FILES_ONLY:
          if( file.isFile() )
          {
            filesList.add( file.getName() );
          }
          break;
        case FOLDERS_ONLY:
          if( file.isDirectory() )
          {
            filesList.add( file.getName() );
          }
          break;
      }
    }
    disconnect( ftp );
    return ( String[] ) filesList.toArray( new String[filesList.size()] );
  }

  public String[] getFolderList( FtpHost ftpAccess, String sourceDir ) throws SocketException, IOException
  {
    return getFiles( ftpAccess, sourceDir, SearchMode.FOLDERS_ONLY );
  }

  public String[] getFileList( FtpHost ftpAccess, String sourceDir ) throws SocketException, IOException
  {
    return getFiles( ftpAccess, sourceDir, SearchMode.FILES_ONLY );
  }

  public boolean renameFile( FtpHost ftpAccess, String fileName, String fileNameNew, String sourceDir )
    throws Exception
  {
    boolean result = false;
    Logger.operation( String.format( FtpMessages.OPERATION_RENAME_FILE.toString(), fileName ) );
    FTPClient ftp = connect( ftpAccess );
    Logger.info( String.format( FtpMessages.CHANGE_WORKING_DIR_PATTERN.toString(), sourceDir ) );
    ftp.changeWorkingDirectory( sourceDir );
    Logger.info( String.format( FtpMessages.FTP_REPLY_STRING_PATTERN.toString(), ftp.getReplyString() ) );
    if( ftp.rename( fileName, fileNameNew ) )
    {
      Logger.info( String.format( FtpMessages.FTP_REPLY_STRING_PATTERN.toString(), ftp.getReplyString() ) );
      result = true;
    }
    Logger.info( String.format( FtpMessages.FTP_REPLY_STRING_PATTERN.toString(), ftp.getReplyString() ) );
    disconnect( ftp );
    return result;
  }

  public boolean createDir( FtpHost ftpAccess, String dirName, String sourceDir ) throws Exception
  {
    boolean result = false;
    Logger.operation( String.format( FtpMessages.OPEARTION_CREATE_DIR_PATTERN.toString(), dirName ) );
    FTPClient ftp = connect( ftpAccess );
    Logger.info( String.format( FtpMessages.CHANGE_WORKING_DIR_PATTERN.toString(), sourceDir ) );
    ftp.changeWorkingDirectory( sourceDir );
    Logger.info( String.format( FtpMessages.FTP_REPLY_STRING_PATTERN.toString(), ftp.getReplyString() ) );
    if( ftp.makeDirectory( dirName ) )
    {
      Logger.info( String.format( FtpMessages.FTP_REPLY_STRING_PATTERN.toString(), ftp.getReplyString() ) );
      result = true;
    }
    Logger.info( String.format( FtpMessages.FTP_REPLY_STRING_PATTERN.toString(), ftp.getReplyString() ) );
    disconnect( ftp );
    return result;
  }

  public String getWorkingDirectory( FtpHost ftpAccess ) throws Exception
  {
    Logger.operation( String.format( FtpMessages.OPERATION_GET_WORK_DIR_PATTERN.toString(), ftpAccess.toString() ) );
    FTPClient ftp = connect( ftpAccess );
    String result = ftp.printWorkingDirectory();
    Logger.info( String.format( FtpMessages.FTP_REPLY_STRING_PATTERN.toString(), ftp.getReplyString() ) );
    disconnect( ftp );
    result = result.trim();
    if( !( result.endsWith( SpecialSymbols.SLASH.toString() ) ) )
    {
      result = result + SpecialSymbols.SLASH.toString();
    }
    return result;
  }
}